/*
 * Project: Sudoku Explainer
 * Copyright (C) 2006-2007 Nicolas Juillerat
 * 
 * Project: Sudoku Hint Engine for Android
 * Copyright (C) 2010-2013 Emanuel Moecklin
 * 
 * Available under the terms of the Lesser General Public License (LGPL)
 */
package com.onegravity.sudoku.solver.rules.fish;

import java.util.ArrayList;
import java.util.BitSet;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import com.onegravity.sudoku.solver.HintResource;
import com.onegravity.sudoku.solver.R;
import com.onegravity.sudoku.solver.accumulator.HintsAccumulator;
import com.onegravity.sudoku.solver.accumulator.StopHintProductionException;
import com.onegravity.sudoku.solver.core.IndirectHint;
import com.onegravity.sudoku.solver.core.IndirectHintProducer;
import com.onegravity.sudoku.solver.data.Block;
import com.onegravity.sudoku.solver.data.Cell;
import com.onegravity.sudoku.solver.data.Column;
import com.onegravity.sudoku.solver.data.ExtraRegionExtended;
import com.onegravity.sudoku.solver.data.Grid;
import com.onegravity.sudoku.solver.data.Region;
import com.onegravity.sudoku.solver.data.Row;
import com.onegravity.sudoku.solver.util.BitSetFactory;

/**
 * Implementation of Pointing and Claiming solving techniques.
 */
public class LockingProducer implements IndirectHintProducer {

	private BitSet potentialPositions;
	
	public LockingProducer() {
		potentialPositions = new BitSet(9);
	}
	
    public void getHints(Grid grid, HintsAccumulator accu) throws StopHintProductionException, InterruptedException {
        if (grid.hasExtraRegions()) {
            getHints(grid, Block.class, ExtraRegionExtended.class, accu);	// pointing
            getHints(grid, Row.class, ExtraRegionExtended.class, accu);		// claiming
            getHints(grid, Column.class, ExtraRegionExtended.class, accu);	// claiming
            getHints(grid, ExtraRegionExtended.class, Block.class, accu);	// claiming
            getHints(grid, ExtraRegionExtended.class, Row.class, accu);		// claiming
            getHints(grid, ExtraRegionExtended.class, Column.class, accu);	// claiming
        }
        getHints(grid, Block.class, Column.class, accu);	// pointing
        getHints(grid, Block.class, Row.class, accu);		// pointing
        getHints(grid, Column.class, Block.class, accu);	// claiming
        getHints(grid, Row.class, Block.class, accu);		// claiming
    }

    /**
     * Given two part types, iterate on pairs of parts of both types that
     * are crossing. For each such pair (p1, p2), check if all the potential
     * positions of a value in p1 are also in p2.
     * <p>
     * Note: at least one of the two part type must be a
     * {@link Block 3x3 square}.
     * @param regionType1 the first part type
     * @param regionType2 the second part type
     * @throws InterruptedException 
     */
    private <S extends Region, T extends Region> void getHints(
            Grid grid, Class<S> regionType1, Class<T> regionType2,
            HintsAccumulator accu) throws StopHintProductionException, InterruptedException {

        // Iterate on pairs of parts
    	for (Region region1:grid.getRegions(regionType1)) {
        	for (Region region2:grid.getRegions(regionType2)) {
                if (Thread.interrupted()) throw new InterruptedException();
                if (region1.crosses(region2)) {
                    List<Cell> region2Cells = region2.getCellSet();
                    // Iterate on values
                    for (int value = 1; value <= 9; value++) {
                        boolean isInCommonSet = true;
                        // Get the potential positions of the value in part1
                        region1.getPotentialPositions(potentialPositions, value);
                        // Note: if cardinality == 1, this is Hidden Single in part1
                        if (potentialPositions.cardinality() > 1) {
                            // Test if all potential positions are also in part2
                            for (int i = 0; i < 9; i++) {
                                if (potentialPositions.get(i)) {
                                    Cell cell = region1.getCell(i);
                                    if (!region2Cells.contains(cell)) {
                                        isInCommonSet = false;
                                    }
                                }
                            }
                            if (isInCommonSet) {
                                // Potential solution found
                                IndirectHint hint = createLockingHint(region1, region2, null, value);
                                if (hint!=null && hint.isWorth()) accu.add(hint);
                            }
                        }
                    } // for each value
                } // if parts are crossing
            }
        }
    }

    private IndirectHint createLockingHint(Region p1, Region p2, Cell hcell, int value) {
        // build removable potentials
        Map<Cell, BitSet> cellRemovablePotentials = null;
        List<Cell> p1Cells = p1.getCellSet();
        for (int i = 0; i < 9; i++) {
            Cell cell = p2.getCell(i);
            if (!p1Cells.contains(cell)) {
                if (cell.hasPotentialValue(value)) {
                	if (cellRemovablePotentials==null) cellRemovablePotentials = new HashMap<Cell, BitSet>();
                    cellRemovablePotentials.put(cell, BitSetFactory.createOneElement(value));
                }
            }
        }
    	if (cellRemovablePotentials==null) return null;
        
    	// highlighted cells
        List<Cell> highlightedCells = new ArrayList<Cell>();
        for (int i = 0; i < 9; i++) {
            Cell cell = p2.getCell(i);
            if (p1Cells.contains(cell) && cell.hasPotentialValue(value)) {
                highlightedCells.add(cell);
            }
        }

    	// build highlighted potentials
        Map<Cell,BitSet> cellPotentials = new HashMap<Cell,BitSet>();
        for (int i = 0; i < 9; i++) {
            Cell cell = p1.getCell(i);
            if (cell.hasPotentialValue(value)) {
                cellPotentials.put(cell, BitSetFactory.createOneElement(value));
            }
        }
        
        // build list of cells
        Cell[] cells = new Cell[highlightedCells.size()];
        highlightedCells.toArray(cells);
        
        // build hint
        return new LockingHint(cells, value, cellPotentials, cellRemovablePotentials, p1, p2);
    }

	@Override
	public String getProducerName() {
    	return HintResource.getString(R.string.producer_locked_candidates);
	}
}